#include <stdio.h>
#include <assert.h>
#include <limits.h>

#include "MPool.h"


/***************************************************************************************************************/
/****************************************    struct MPool    ***************************************************/
/***************************************************************************************************************/
struct MPool_t
{
    size_t              m_blockSize;
    unsigned short      m_firstFree;
};
/***************************************************************************************************************/

MPool* MPCreate(size_t _blockSize, size_t _numBlocks, void* _mem)
{
    MPool*          mpool   = 0 ;
    char*           memPtr  = 0 ;
    unsigned short  i           ;

    assert(_mem);
    assert(_numBlocks <= USHRT_MAX);                /*  max blocks <= maximum of unsigned short */
    assert(_blockSize >= sizeof(unsigned short));   /*  min of blocksize is 2 bytes             */
    assert(MP_OVERHEAD == sizeof(MPool));           /*  Check the correctnce of MACRO           */

    mpool              = _mem      ;
    mpool->m_blockSize = _blockSize;
    mpool->m_firstFree = 1         ;                /*  The firstFree is the first (== '1')     */

    for(memPtr = (char*)_mem + sizeof(MPool),i = 0; i < _numBlocks - 1; ++i, memPtr += _blockSize)
    {
        *((unsigned short*)memPtr) = i + 2;          /*  The first block points to '2'           */
    }

    *((unsigned short*)memPtr) = 0;                  /*  The last one points to '0'              */

    return mpool;
}


void* MPAlloc(MPool* _mpool)
{
    void* allocBlock = 0;

    assert(_mpool);
    /*  If all allocations used return 0.
     *  Other choice was to assert it in debug and hand the responsible to user in release.
     *  But fail in malloc return NULL, so this is the implemntation here.                  */
    if (!_mpool->m_firstFree)
    {
        return 0;
    }

    allocBlock = (char*)_mpool + sizeof(MPool) + (_mpool->m_firstFree - 1) * (_mpool->m_blockSize);
    _mpool->m_firstFree = *((unsigned short*)allocBlock);

    return allocBlock;
}


void MPFree(MPool* _mpool, void* _bp)
{
    assert(_mpool);
    assert(_bp);
    assert((char*)_bp >= (char*)_mpool + sizeof(MPool));                    /*  The pointer above the sizeof(MPool)                 */

    *((unsigned short*)(_bp))    = _mpool->m_firstFree;                     /*  The free block will point to the prev first free    */

    _mpool->m_firstFree   = ( ( (char*)_bp - ((char*)_mpool + sizeof(MPool)) )/_mpool->m_blockSize ) + 1;     /*  The dif between ((the offset and the block pointer)/blockSize) + 1 == index */
}


size_t MPUnused(MPool* _mpool)
{
    unsigned short  nextBlock   = 0 ;
    size_t          count       = 0 ;

    assert(_mpool);

    nextBlock = _mpool->m_firstFree;

    while(nextBlock != 0)
    {
        ++count;
        nextBlock = *( (unsigned short*)( (char*)_mpool + sizeof(MPool) + (nextBlock - 1)*(_mpool->m_blockSize) ) );
    }

    return count;
}

/***************************************************************************************************************/
/*********************************************    MPool_UTEST    *************************************************/
/***************************************************************************************************************/
#ifdef MPOOL_UTEST
#include <stdlib.h>
#define IS_SUCCESS(X) !X?"SUCCESS":"FAILURE"   /*  Macro to print on regression tests       */
#define IF_FAIL(X) if(X){printf("FAIL IN %s TEST, IN LINE %d\n",#X,__LINE__); return 1;}
/*******************    Forward Declerations    ******************/
int MPoolRegression_Utest();
int MPoolCreate_Utest();
int MPoolAlloc_Utest();
int MPoolFree_Utest();
int MPoolUnused_Utest();
/*****************************************************************/

int MPoolRegression_Utest()
{
    printf("MPoolCreate_Utest: %s\n\n", IS_SUCCESS(MPoolCreate_Utest()));
    printf("MPoolAlloc_Utest: %s\n\n", IS_SUCCESS(MPoolAlloc_Utest()));
    printf("MPoolFree_Utest: %s\n\n", IS_SUCCESS(MPoolFree_Utest()));
    printf("MPoolUnused_Utest: %s\n", IS_SUCCESS(MPoolUnused_Utest()));

    return 0;
}

int main()
{
    MPoolRegression_Utest();

    return 0;
}

int MPoolCreate_Utest()
{
    int     blockSize   = 4                                         ;
    int     numBlock    = 5                                         ;
    void*   heap        = malloc(blockSize * numBlock + MP_OVERHEAD);
    MPool*  mp          = MPCreate(blockSize, numBlock ,heap)       ;

    IF_FAIL((void*)mp != heap)
    IF_FAIL( *((unsigned short*)((char*)mp + blockSize + sizeof(MPool))) != 3)

    IF_FAIL( *((unsigned short*)((char*)mp + blockSize*(numBlock - 1) + sizeof(MPool))) != 0)

    return 0;
}


int MPoolAlloc_Utest()
{
    int     blockSize   = 4                                         ;
    int     numBlock    = 5                                         ;
    void*   heap        = malloc(blockSize * numBlock + MP_OVERHEAD);
    MPool*  mp          = MPCreate(blockSize, numBlock ,heap)       ;
    int* cp = MPAlloc(mp);

    IF_FAIL(MPUnused(mp) != numBlock - 1)
    IF_FAIL((char*)cp != (char*)mp + sizeof(MPool) )
    IF_FAIL(*((unsigned short*)((char*)mp + sizeof(MPool) + blockSize)) != 3)
    IF_FAIL(mp->m_firstFree != 2)     /*  Check that firstfree updated to 2 (the first was 1)  */

    cp = MPAlloc(mp);
    IF_FAIL((char*)cp != (char*)mp +blockSize + sizeof(MPool) )
    IF_FAIL(mp->m_firstFree != 3)

    return 0;
}

int MPoolFree_Utest()
{
    int     blockSize   = 4                                         ;
    int     numBlock    = 5                                         ;
    void*   heap        = malloc(blockSize * numBlock + MP_OVERHEAD);
    MPool*  mp          = MPCreate(blockSize, numBlock ,heap)       ;
    int* ip1 = MPAlloc(mp);
    int* ip2 = MPAlloc(mp);
    int* ip3 = 0;

    MPFree(mp, ip1);

    *ip2 = 5;
    IF_FAIL(mp->m_firstFree != 1)
    ip3 = MPAlloc(mp);
    IF_FAIL((char*)ip3 != (char*)mp + sizeof(MPool))
    IF_FAIL(MPUnused(mp) != numBlock - 2)

    return 0;
}

int MPoolUnused_Utest()
{
    int     blockSize   = 4                                         ;
    int     numBlock    = 5                                         ;
    void*   heap        = malloc(blockSize * numBlock + MP_OVERHEAD);
    MPool*  mp          = MPCreate(blockSize, numBlock ,heap)       ;
    int* ip1;
    int i;

    IF_FAIL(MPUnused(mp) != numBlock)
    for(i = 0; i < numBlock ; ++i)
    {
        ip1 = MPAlloc(mp);
    }
    IF_FAIL(MPUnused(mp))           /*  Check that everyone used    */
    IF_FAIL(mp->m_firstFree)
    MPFree(mp , ip1);
    i = mp->m_firstFree;
    IF_FAIL(MPUnused(mp) != 1)
    free(heap);

    return 0;
}
#endif /*   MPOOL_UTEST */
